<?php

$data = file_get_contents(__DIR__.'/data.json');
$json = Json::js4obj($data);
var_dump($json);

/**
 * Json 解析范类
 */
class Json
{

    /**
     * 对象
     */
    CONST OBJECT_S  = 1;

    /**
     * 对象结束
     */
    CONST OBJECT_E  = 2;

    /**
     * 数组开始
     */
    CONST ARRAY_S   = 4;

    /**
     * 数组结束
     */
    CONST ARRAY_E   = 8;

    /**
     * 空值
     */
    CONST NULL_C    = 16;

    /**
     * 数字
     */
    CONST NUMBER_C  = 32;

    /**
     * 支付串
     */
    CONST STRING_C  = 64;

    /**
     * 布尔值
     */
    CONST BOOLEAN_C = 128;

    /**
     * :
     */
    CONST SEP_COLON = 256;

    /**
     * ,
     */
    CONST SEP_COMMA = 512;

    /**
     * 解析结束
     */
    CONST DOCUMENT_END = 1024;


    /**
     * JavaScript 对象字符串解析
     * 
     * @param  string $json 字符串
     * @return array|null
     */
    public function js_decode($json = '')
    {
        if (empty($json)) return null;

        $token  = new Tokenize($json);
        $tokens = $token->tokenize();

        $parse = new JsonParse($tokens);
        $json  = $parse->parse_obj();
        return array_pop($json);
    }

    static public function js4obj($json)
    {
        $obj = new Json();
        return $obj->js_decode($json);
    }
}


/**
 * JsonParse 语法解析器
 */
class JsonParse
{
    
    public function __construct($tokens, $dict = true)
    {
        $this->index = 0;
        $this->tokens= $tokens;
        $this->lenght= count($tokens);
        $this->dict  = $dict;
    }


    /**
     * 解析 token
     * @param  boolean $begin  是否开头解析
     * @return array
     */
    public function parse_obj()
    {
        $key          = null;
        $json_obj     = array();
        $expect_token = Json::OBJECT_S|Json::OBJECT_E|Json::STRING_C;

        while ($this->has_more()) {

            list($type, $val) = $this->next_token();

            switch ($type) {
                case Json::OBJECT_S:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[$key] = $this->parse_obj();
                    $expect_token = Json::SEP_COMMA|Json::OBJECT_E;
                }
                break;
                case Json::OBJECT_E:
                {
                    $this->check_expect($type, $expect_token);
                    return $json_obj;
                }
                case Json::ARRAY_S:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[$key] = $this->parse_arr();
                    $expect_token = Json::SEP_COMMA|Json::OBJECT_E;
                }
                break;
                case Json::NULL_C:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[$key] = null;
                    $expect_token = Json::SEP_COMMA|Json::OBJECT_E;
                }
                break;
                case Json::NUMBER_C:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[$key] = $val + 0;
                    $expect_token = Json::SEP_COMMA|Json::OBJECT_E;
                }
                break;
                case Json::STRING_C:
                {
                    // 区分类型为键值, 自己挖的坑,注意取前一个
                    $this->check_expect($type, $expect_token);
                    list($pre_type, $pre_val) = $this->pre_token(2);

                    if ($pre_type == Json::SEP_COLON) { // 前一个是 :
                        $json_obj[$key] = $val;
                        $expect_token   = Json::SEP_COMMA|Json::OBJECT_E;
                    } else {
                        $key = $val;
                        $expect_token = Json::SEP_COLON;
                    }
                }
                break;
                case Json::BOOLEAN_C:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[$key] = ($val === 'true');
                    $expect_token = Json::SEP_COMMA|Json::OBJECT_E;
                }
                break;
                case Json::SEP_COLON: // ':'
                {
                    $this->check_expect($type, $expect_token);
                    $expect_token = Json::OBJECT_S
                        | Json::ARRAY_S
                        | Json::NULL_C
                        | Json::NUMBER_C
                        | Json::STRING_C
                        | Json::BOOLEAN_C;
                }
                break;
                case Json::SEP_COMMA: // ','
                {
                    $this->check_expect($type, $expect_token);
                    $expect_token = Json::STRING_C;
                }
                break;
                case Json::DOCUMENT_END:
                    return $json_obj;
            }
        }
        throw new Exception("token 错误", 1);
    }


    /**
     * 解析array
     */
    public function parse_arr()
    {
        $json_obj = array();
        $expect_token = Json::OBJECT_S
            | Json::ARRAY_S
            | Json::NULL_C
            | Json::NUMBER_C
            | Json::STRING_C
            | Json::BOOLEAN_C;

        while ($this->has_more()) {
            
            list($type, $val) = $this->next_token();

            switch ($type) {
                case Json::OBJECT_S:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[]   = $this->parse_obj();
                    $expect_token = Json::SEP_COMMA|Json::ARRAY_E;
                }
                break;
                case Json::ARRAY_S:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[]   = $this->parse_arr();
                    $expect_token = Json::SEP_COMMA|Json::ARRAY_E;
                }
                break;
                case Json::ARRAY_E:
                    return $json_obj;
                case Json::NULL_C:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[]   = null;
                    $expect_token = Json::SEP_COMMA|Json::ARRAY_E;
                }
                break;
                case Json::NUMBER_C:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[]   = 0 + $val;
                    $expect_token = Json::SEP_COMMA|Json::ARRAY_E;
                }
                break;
                case Json::STRING_C:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[]   = $val;
                    $expect_token = Json::SEP_COMMA|Json::ARRAY_E;
                }
                break;
                case Json::BOOLEAN_C:
                {
                    $this->check_expect($type, $expect_token);
                    $json_obj[]   = ($val === 'true');
                    $expect_token = Json::SEP_COMMA|Json::ARRAY_E;
                }
                break;
                case Json::SEP_COMMA:
                {
                    $this->check_expect($type, $expect_token);
                    $expect_token = Json::OBJECT_S
                        | Json::ARRAY_S
                        | Json::NULL_C
                        | Json::NUMBER_C
                        | Json::STRING_C
                        | Json::BOOLEAN_C;
                }
                break;
                case Json::DOCUMENT_END:
                    return $json_obj;
            }
        }
        throw new Exception("token 错误", 1);
    }


    /**
     * 期望标签检查
     */
    private function check_expect($type, $expect_token)
    {
        if (($type & $expect_token) == 0) {
            throw new Exception("无效的 token\n", 1);
        }
    }


    /**
     * 取出当前token, 且指针移到下一个
     */
    private function next_token()
    {
        return $this->tokens[$this->index++];
    }


    /**
     * 取当前位置 token
     */
    private function peek_token()
    {
        if ($this->index == 0) {
            return null;
        }
        return $this->tokens[$this->index];
    }


    /**
     * 第前n个 token
     */
    private function pre_token($num = 1)
    {
        if ($this->index < $num) {
            return null;
        }
        return $this->tokens[($this->index - $num)];
    }


    /**
     * 是否有更多的值
     */
    private function has_more()
    {
        return ($this->index < $this->lenght);
    }
}


/**
 * 词法解析器
 */
class Tokenize
{

    public function __construct($json)
    {
        $this->index  = 0;
        $this->lenght = strlen($json);
        $this->json   = $json;
    }

    public function tokenize()
    {
        $token  = 0;
        $tokens = [];
        while (($token = $this->start()) !== -1)
            $tokens[] = $token;
        $tokens[] = [Json::DOCUMENT_END, true];
        return $tokens;
    }


    /**
     * 序列标记化
     * 
     * @param  string $json 原始字符串
     * @return array
     */
    private function start()
    {
        $ch = null;

        while (true) {
            if ($this->has_more() === false)
                return -1;

            $ch = $this->next_char();
            if ($ch !== ' '
                && $ch !== '\n'
                && $ch !== '\t')
                break;
        }

        switch ($ch) {
            case '{':
                return [Json::OBJECT_S, $ch];
            case '}':
                return [Json::OBJECT_E, $ch];
            case '[':
                return [Json::ARRAY_S , $ch];
            case ']':
                return [Json::ARRAY_E , $ch];
            case 'n':
                return $this->read_null();
            case '"':
                return $this->read_sting();
            case 't':
            case 'f':
                return $this->read_boolean($ch);
            case ':':
                return [Json::SEP_COLON, $ch];
            case ',':
                return [Json::SEP_COMMA, $ch];
            default:
                if (is_numeric($ch)) {
                    return $this->read_number($ch);
                }
        }

        $char = $this->json[$this->index];
        throw new Exception("非法字符!!!位置:{$this->index}, 字符:{$char} .", 1);
    }


    /**
     * 解析空值
     * 
     * @return [type] [description]
     */
    public function read_null()
    {
        $ch1 = $this->next_char();
        $ch2 = $this->next_char();
        $ch3 = $this->next_char();
        if ($ch1 === 'u'
            && $ch2 === 'l'
            && $ch2 === 'l') {
            return [Json::NULL_C, 'null'];
        }
        throw new Exception("解析 null 错误!index:{$this->index}", 1);
    }

    /**
     * 解析数字
     * @return [type] [description]
     */
    private function read_number($ch)
    {
        $str = $ch;
        while ($this->has_more()) {
            $ch  = $this->next_char();
            $asc = ord($ch);
            if (($asc>47 && $asc < 58) || $asc == 46) {
                $str .= $ch;
                continue;
            }
            break;
        }
        $this->index--;
        return [Json::NUMBER_C, $str];
    }


    /**
     * 解析字符串
     * @return [type] [description]
     */
    public function read_sting()
    {
        // TODO::解析转义字符 \"
        $str = '';
        while (($ch = $this->next_char()) !== false) {
            if ($ch === '\\') {

                // 判断一般转义字符
                if (!$this->escape())
                    throw new Exception("转义字符解析错误!位置:{$this->index}, 字符:{$ch}", 1);
                $str .= $ch.$this->peek_char();

                // Unicode 字符
                if (($ch = $this->next_char()) == 'u') {
                    for ($i=0; $i < 4; $i++) {
                        $ch = $this->next_char();
                        if (!$this->hex($ch))
                            throw new Exception("转义字符解析错误!位置:{$this->index}, 字符:{$ch}", 1);
                        $str .= $ch;
                    }
                }

            } elseif ($ch == '"') {
                break;

            } else {
                $str .= $ch;
            }
        }
        return [Json::STRING_C, $str];
    }


    /**
     * 判断转义字符
     * @return [type] [description]
     */
    private function escape()
    {
        static $map = [ '"', '.', '\\', 'b', 'f', 'n', 'r', 't', 'u', '/'];
        $ch = $this->peek_char();
        return in_array($ch, $map);
    }


    /**
     * 是否为hex
     * @param  [type] $ch [description]
     * @return [type]     [description]
     */
    private function hex($ch)
    {
        // 注意Unicode 为 16进制表示
        return ($ch >= '0' && $ch <= '9')
            || ($ch >= 'a' && $ch<= 'f')
            || ($ch >= 'A' && $ch<= 'F');
    }


    /**
     * 解析布尔值
     * @param  [type] $ch [description]
     * @return [type]     [description]
     */
    public function read_boolean($ch)
    {
        $ch1 = $this->next_char();
        $ch2 = $this->next_char();
        $ch3 = $this->next_char();
        if ($ch === 't') {
            // true
            if ($ch1 === 'r'
                && $ch2 === 'u'
                && $ch3 === 'e')
                return [Json::BOOLEAN_C, true];

        } elseif ($ch === 'f') {
            // false
            $ch4 = $this->next_char();
            if ($ch1 === 'a'
                && $ch2 === 'l'
                && $ch3 === 's'
                && $ch4 === 'e')
                return [Json::BOOLEAN_C, false];
        }

        throw new Exception("布尔值类型参数错误!位置{$this->index}", 1);
    }


    /**
     * 是否还有字符串
     * 
     * @return boolean
     */
    public function has_more()
    {
        return ($this->index < $this->lenght);
    }


    /**
     * 遍历字符
     * 
     * @param  string $json 
     * @return string
     */
    public function next_char()
    {
        if ($this->index < $this->lenght)
            return ($this->json)[$this->index++];

        return false;
    }


    /**
     * 获取下标字符
     * @return [type] [description]
     */
    public function peek_char()
    {
        return ($this->json)[$this->index];
    }

}